﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HorizontalTextBoxCompareControl.cs" company="" />
// <summary>
//   Enumerates the kind of the value displayed by the <see cref="HorizontalTextBoxCompareControl" /> control.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Xompare
{
    using System;
    using System.Drawing;
    using System.Globalization;
    using System.Reflection;
    using System.Windows.Forms;

    using DiffPlex;

    using FormsUtilities;

    using Xompare.Helpers;

    /// <summary>
    /// Enumerates the kind of the value displayed by the <see cref="HorizontalTextBoxCompareControl"/> control.
    /// </summary>
    public enum XmlValueKind
    {
        None,
        ElementValue,
        AttributeValue
    }

    public partial class HorizontalTextBoxCompareControl : UserControl
    {
        #region Constants

        private const string c_elementValue = "Element Value";
        private const string c_attributeValue = "Attribute Value";

        private static readonly Color s_diffBackColor = Color.LightBlue;

        #endregion

        /// <summary>
        /// The measured size of a letter in the text-boxes.
        /// </summary>
        private SizeF m_letterSize;

        public HorizontalTextBoxCompareControl()
        {
            InitializeComponent();

            InitializeControl();
        }

        #region Public exported API

        /// <summary>
        /// Gets or sets the text on the 'Origin' text-box.
        /// </summary>
        public string OriginText
        {
            get
            {
                return originRichTextBox.Text;
            }

            set
            {
                originRichTextBox.Text = value;
                if (string.IsNullOrEmpty(value))
                {
                    OriginValueKind = XmlValueKind.None;
                }
            }
        }

        /// <summary>
        /// Gets or sets the text on the 'Target' text-box.
        /// </summary>
        public string TargetText
        {
            get
            {
                return targetRichTextBox.Text;
            }

            set
            {
                targetRichTextBox.Text = value;
                if (string.IsNullOrEmpty(value))
                {
                    TargetValueKind = XmlValueKind.None;
                }
            }
        }

        /// <summary>
        /// Gets or sets the kind of the value presented by the 'original' text-box.
        /// </summary>
        private XmlValueKind m_originValueKind = XmlValueKind.None;
        public XmlValueKind OriginValueKind
        {
            get
            {
                return m_originValueKind;
            }

            set
            {
                switch (value)
                {
                    case XmlValueKind.None:
                    {
                        originPictureBox.Visible = false;
                        originToolTip.ToolTipTitle = string.Empty;

                        break;
                    }

                    case XmlValueKind.ElementValue:
                    {
                        originPictureBox.Visible = true;
                        originPictureBox.Image = MiscResources.GetImage(Assembly.GetExecutingAssembly(), XompareResourceNames.CElementImageName);
                        originToolTip.ToolTipTitle = c_elementValue;

                        break;
                    }

                    case XmlValueKind.AttributeValue:
                    {
                        originPictureBox.Visible = true;
                        originPictureBox.Image = MiscResources.GetImage(Assembly.GetExecutingAssembly(), XompareResourceNames.CAttributeImageName);
                        originToolTip.ToolTipTitle = c_attributeValue;

                        break;
                    }

                    default:
                    {
                        throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} is not a recognized 'XmlValueKind' value.", value));
                    }
                }

                m_originValueKind = value;
            }
        }

        /// <summary>
        /// Gets or sets the kind of the value presented by the 'target' text-box.
        /// </summary>
        private XmlValueKind m_targetValueKind = XmlValueKind.None;
        public XmlValueKind TargetValueKind
        {
            get
            {
                return m_targetValueKind;
            }

            set
            {
                switch (value)
                {
                    case XmlValueKind.None:
                        {
                            targetPictureBox.Visible = false;
                            targetToolTip.ToolTipTitle = string.Empty;

                            break;
                        }

                    case XmlValueKind.ElementValue:
                        {
                            targetPictureBox.Visible = true;
                            targetPictureBox.Image = MiscResources.GetImage(Assembly.GetExecutingAssembly(), XompareResourceNames.CElementImageName);
                            targetToolTip.ToolTipTitle = c_elementValue;

                            break;
                        }

                    case XmlValueKind.AttributeValue:
                        {
                            targetPictureBox.Visible = true;
                            targetPictureBox.Image = MiscResources.GetImage(Assembly.GetExecutingAssembly(), XompareResourceNames.CAttributeImageName);
                            targetToolTip.ToolTipTitle = c_attributeValue;

                            break;
                        }

                    default:
                        {
                            throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "{0} is not a recognized 'XmlValueKind' value.", value));
                        }
                }

                m_targetValueKind = value;
            }
        }

        /// <summary>
        /// Gets or sets whether to allow difference coloring when the text changes. When the value changes 'false' -> 'true', a difference coloring is taking place.
        /// </summary>
        private bool m_allowDifferenceColoring = true;
        public bool AllowDifferenceColoring
        {
            get
            {
                return m_allowDifferenceColoring;
            }

            set
            {
                if (!m_allowDifferenceColoring && value)
                {
                    m_allowDifferenceColoring = true;

                    ColorDifferences();
                }

                m_allowDifferenceColoring = value;
            }
        }

        #endregion

        #region Private helpers

        /// <summary>
        /// Sets the size and ratio of the scrollbar according to the text controls.
        /// </summary>
        private void SetScrollbarByTexts()
        {
            var largestString = originRichTextBox.Text.Length > targetRichTextBox.Text.Length ? originRichTextBox.Text : targetRichTextBox.Text;

            using (var graphics = CreateGraphics())
            {
                var textSize = graphics.MeasureString(largestString, originRichTextBox.Font);
                var textClientSize = originRichTextBox.ClientSize;

                if (textClientSize.Width >= textSize.Width)
                {
                    scrollBarAggregate.Maximum = 0;
                }
                else
                {
                    scrollBarAggregate.Maximum = (int)textSize.Width;
                    scrollBarAggregate.LargeChange = textClientSize.Width;
                }
            }
        }

        /// <summary>
        /// Initializing regarding the user-control.
        /// </summary>
        private void InitializeControl()
        {
            // Measure the size of the used font letter (assuming fixed-font):
            using (var graphics = CreateGraphics())
            {
                m_letterSize = graphics.MeasureString("A", originRichTextBox.Font);
            }

            // Set the scroll-bar accordingly:
            scrollBarAggregate.SmallChange = (int)m_letterSize.Width;
        }

        /// <summary>
        /// Calculate the difference between the original and target texts, and colorize them to be apparent.
        /// Does not color the texts if either is empty.
        /// </summary>
        private void ColorDifferences()
        {
            if (!AllowDifferenceColoring)
            {
                return;
            }

            if (string.IsNullOrEmpty(originRichTextBox.Text) || string.IsNullOrEmpty(targetRichTextBox.Text))
            {
                return;
            }

            var differ = new Differ();
            var diffModel = differ.CreateCharacterDiffs(originRichTextBox.Text, targetRichTextBox.Text, false);

            // Clear the last color marking:
            ColorTextInBox(originRichTextBox, 0, originRichTextBox.TextLength, originRichTextBox.BackColor);
            ColorTextInBox(targetRichTextBox, 0, targetRichTextBox.TextLength, targetRichTextBox.BackColor);

            foreach (var diff in diffModel.DiffBlocks)
            {
                ColorTextInBox(originRichTextBox, diff.DeleteStartA, diff.DeleteCountA, s_diffBackColor);
                ColorTextInBox(targetRichTextBox, diff.InsertStartB, diff.InsertCountB, s_diffBackColor);
            }
        }

        /// <summary>
        /// Mark part of a text in a given <see cref="RichTextBox"/> with specific color.
        /// </summary>
        /// <param name="container">The text box container.</param>
        /// <param name="startPosition">The starting position index (0-based).</param>
        /// <param name="length">The length of the mark.</param>
        /// <param name="markingColor">The color of the mark.</param>
        private void ColorTextInBox(RichTextBox container, int startPosition, int length, Color markingColor)
        {
            // Make sure starting position is not out of boundries:
            if (startPosition >= container.TextLength)
            {
                return;
            }

            // Make sure that the marked text is not out of boundries:
            if (startPosition + length - 1 >= container.TextLength)
            {
                length = container.TextLength - startPosition;
            }

            container.SelectionStart = startPosition;
            container.SelectionLength = length;
            container.SelectionBackColor = markingColor;
        }

        #endregion

        private void originRichTextBox_TextChanged(object sender, EventArgs e)
        {
            SetScrollbarByTexts();

            ColorDifferences();
        }

        private void targetRichTextBox_TextChanged(object sender, EventArgs e)
        {
            SetScrollbarByTexts();

            ColorDifferences();
        }

        private void HorizontalTextBoxCompareControl_Resize(object sender, EventArgs e)
        {
            SetScrollbarByTexts();
        }

        private void scrollBarAggregate_Scroll(object sender, ScrollEventArgs e)
        {
            var messageValue = (IntPtr)(e.NewValue * (1 << 16) + WinMsgUtils.ScrollBarCommands.SB_THUMBPOSITION);

            WinMsgUtils.SendMessage(originRichTextBox.Handle, WinMsgUtils.WM_HSCROLL, messageValue, IntPtr.Zero);
            WinMsgUtils.SendMessage(targetRichTextBox.Handle, WinMsgUtils.WM_HSCROLL, messageValue, IntPtr.Zero);
        }
    }
}
